/*
 * BeamWars.java - A remake of the Mac classic shareware game
 * 
 * Updated on August 18, 2009 by Ming Chow
 */

package game.mchow.beamwars;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
import java.util.*;

public class BeamWars extends JPanel implements Runnable, KeyListener
{
	private static final long serialVersionUID = 1L;
	
	// Constants
	public static final int DEFAULT_SCREEN_WIDTH = 800;
	public static final int DEFAULT_SCREEN_HEIGHT = 720;
	public static final int OFFSET = 120;	
	public static final int STARTING_TILE_SIZE = 32;
	private final int DEFAULT_FPS = 10;
	public static final int MAX_PLAYERS = 4;
	private Thread animator;
	private boolean gameRunning, newRound;
	private int [][] grid;
	private int tileSize, gridWidth, gridHeight, boardEnergy, round, fps, numPlayers;
	private ArrayList <Player> players;

	public int getMark (int x, int y)
	{
		if (x >= 0 && x < gridWidth && y >=0 && y < gridHeight) {
			return grid[x][y];
		}
		return -1;
	}
	
	public boolean setMark (int x, int y, Player p)
	{
		if (x >= 0 && x < gridWidth && y >=0 && y < gridHeight) {
			if (grid[x][y] == 0) {
				grid[x][y] = p.getId();
				return true;
			}
			else {
				p.eliminate();
				numPlayers--;
				return false;
			}
		}
		p.eliminate();
		numPlayers--;
		return false;
	}
	
	public boolean isGameRunning() {
		return gameRunning;
	}

	public int[][] getGrid() {
		return grid;
	}

	public int getGridWidth() {
		return gridWidth;
	}

	public int getGridHeight() {
		return gridHeight;
	}

	public int getBoardEnergy() {
		return boardEnergy;
	}

	public int getRound() {
		return round;
	}

	public int getFps() {
		return fps;
	}

	public int getNumPlayers() {
		return numPlayers;
	}

	public BeamWars()
	{
		gameRunning = true;
		round = 0;
		tileSize = STARTING_TILE_SIZE;
		fps = DEFAULT_FPS;
		resetGrid();
		
		// Initialize the players
		players = new ArrayList <Player> (MAX_PLAYERS);
		players.add(new Player(1, Color.GREEN, true, this));
		players.add(new Player(2, Color.RED, true, this));
		players.add(new Player(3, Color.YELLOW, false, this));
		players.add(new Player(4, Color.MAGENTA, false, this));
		
		addKeyListener(this);
		setFocusable(true);
	}
	
	public void resetGrid()
	{
		round++;
		if (round % 8 == 0 && tileSize > 8) {
			tileSize = tileSize / 2;
		}
		gridWidth = DEFAULT_SCREEN_WIDTH / tileSize;
		gridHeight = (DEFAULT_SCREEN_HEIGHT - OFFSET) / tileSize;
		grid = new int[gridWidth][gridHeight];
		newRound = true;
		boardEnergy = 0;
		numPlayers = MAX_PLAYERS;
		for (int i = 0; i < gridWidth; i++) {
			for (int j = 0; j < gridHeight; j++) {
				grid[i][j] = 0;
			}
		}
	}
	
	public void paintComponent (Graphics g)
	{
		Graphics2D g2d = (Graphics2D)g;
		g2d.setColor(Color.WHITE);
		g2d.clearRect(0, 0, this.getWidth(), this.getHeight());
		for (int i = 0; i < gridWidth; i++) {
			for (int j = 0; j < gridHeight; j++) {
				switch (grid[i][j]) {
					case 1:
						g2d.setColor(players.get(0).getColor());
						break;
					case 2:
						g2d.setColor(players.get(1).getColor());
						break;
					case 3:
						g2d.setColor(players.get(2).getColor());
						break;
					case 4:
						g2d.setColor(players.get(3).getColor());
						break;
					default:
						g2d.setColor(Color.BLUE);
				}
				g2d.fillRect(i * tileSize, j * tileSize, tileSize, tileSize);
			}
		}
		g2d.setColor(Color.BLACK);
		g2d.drawString("Player 1: " + players.get(0).getScore() + " (Win " + players.get(0).getNumWins() + ")", 50, DEFAULT_SCREEN_HEIGHT - OFFSET + 20);
		g2d.drawString("Player 2: " + players.get(1).getScore() + " (Win " + players.get(1).getNumWins() + ")", 50, DEFAULT_SCREEN_HEIGHT - OFFSET + 40);
		g2d.drawString("Player 3: " + players.get(2).getScore() + " (Win " + players.get(2).getNumWins() + ")", 50, DEFAULT_SCREEN_HEIGHT - OFFSET + 60);
		g2d.drawString("Player 4: " + players.get(3).getScore() + " (Win " + players.get(3).getNumWins() + ")", 50, DEFAULT_SCREEN_HEIGHT - OFFSET + 80);
		g2d.drawString("Round: " + round, 575, DEFAULT_SCREEN_HEIGHT - OFFSET + 20);
		g2d.drawString("Board Energy: " + boardEnergy, 575, DEFAULT_SCREEN_HEIGHT - OFFSET + 40);
		if (!gameRunning) {
			g2d.drawString("GAME PAUSED!", 575, DEFAULT_SCREEN_HEIGHT - OFFSET + 60);
		}
	}
	
	public void run()
	{
		long start = System.currentTimeMillis();
		Thread current = Thread.currentThread();
		
		while (current == animator) {
			if (gameRunning) {
				try {
					// Delay depending on how far we are behind
					start += (1000 / fps);
					Thread.sleep(Math.max(0, start - System.currentTimeMillis()));
				}
				catch (InterruptedException e) {
					System.err.println("***** Something is wrong with the thread: " + e.toString());
				}
				
				// Display the next frame of animation
				repaint();
			
				// Small pause before a new round begins
				if (newRound) {
					try {
						Thread.sleep(2000);
					}
					catch (Exception se) {
						System.err.println("***** Can't sleep: " + se.toString());
					}
					newRound = false;
				}
				for (int count = 0; count < MAX_PLAYERS; count++) {
					Player p = players.get(count);
					p.update();
					if (!p.isEliminated()) {
						p.move();
					}
				}
				boardEnergy++;
				
				// We have a winner
				if (numPlayers <= 1) {
					try {
						Thread.sleep(2000);
					}
					catch (Exception se) {
						System.err.println("***** Can't sleep: " + se.toString());
					}
					
					// Find the winner
					if (!players.get(0).isEliminated()) {
						players.get(0).win(boardEnergy);
					}
					else if (!players.get(1).isEliminated()) {
						players.get(1).win(boardEnergy);
					}
					else if (!players.get(2).isEliminated()) {
						players.get(2).win(boardEnergy);
					}
					else if (!players.get(3).isEliminated()) {
						players.get(3).win(boardEnergy);
					}
					resetGrid();
					for (int count = 0; count < MAX_PLAYERS; count++) {
						players.get(count).reset();
						fps++;
					}
				}
				
				// Figure out how fast it is running
				if (start + 1000 < System.currentTimeMillis()) {
					start = System.currentTimeMillis();
				}
			}
		}
	}
	
	public void start()
	{	
		animator = new Thread(this);
		animator.start();
	}
	
	public void stop()
	{
		animator = null;
	}

	public void keyPressed(KeyEvent e)
	{
		int key = e.getKeyCode();
		
		switch(key) {
			case KeyEvent.VK_UP:
				players.get(0).setDirection(Player.UP);
				break;
			case KeyEvent.VK_DOWN:
				players.get(0).setDirection(Player.DOWN);
				break;
			case KeyEvent.VK_LEFT:
				players.get(0).setDirection(Player.LEFT);
				break;
			case KeyEvent.VK_RIGHT:
				players.get(0).setDirection(Player.RIGHT);
				break;
			case KeyEvent.VK_P:
				if (gameRunning) {
					gameRunning = false;
				}
				else {
					gameRunning = true;
				}
				break;
			case KeyEvent.VK_W:
				players.get(1).setDirection(Player.UP);
				break;
			case KeyEvent.VK_S:
				players.get(1).setDirection(Player.DOWN);
				break;
			case KeyEvent.VK_A:
				players.get(1).setDirection(Player.LEFT);
				break;
			case KeyEvent.VK_D:
				players.get(1).setDirection(Player.RIGHT);
				break;
		}
	}

	public void keyReleased(KeyEvent e) {}

	public void keyTyped(KeyEvent e) {}
	
	public static void main (String [] args)
	{
		JFrame theFrame = new JFrame();
		BeamWars thePanel = new BeamWars();
		//thePanel.setDoubleBuffered(true);
		thePanel.start();
		theFrame.add(thePanel);
		theFrame.setTitle("BeamWars");
		theFrame.setSize(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT);
		theFrame.setResizable(false);
		theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		theFrame.setVisible(true);
	}
}
